﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization.Binary
{

    public sealed class SizeOptimizedBinaryWriter : IBinaryWriter
    {
        private readonly Encoding _encoding;
        private readonly ITypeNameConverter _typeNameConverter;
        private List<WriteCommand> _cache;
        private IndexGenerator<string> _names;
        private Stream _stream;
        private IndexGenerator<Type> _types;

        public SizeOptimizedBinaryWriter(ITypeNameConverter typeNameConverter, Encoding encoding)
        {
            if (typeNameConverter == null) throw new ArgumentNullException("typeNameConverter");
            if (encoding == null) throw new ArgumentNullException("encoding");
            _encoding = encoding;
            _typeNameConverter = typeNameConverter;
        }

        public void WriteElementId(byte id)
        {
            _cache.Add(new ByteWriteCommand(id));
        }

        public void WriteType(Type type)
        {
            int typeIndex = _types.GetIndexOfItem(type);
            _cache.Add(new NumberWriteCommand(typeIndex));
        }

        public void WriteName(string name)
        {
            int nameIndex = _names.GetIndexOfItem(name);
            _cache.Add(new NumberWriteCommand(nameIndex));
        }

        public void WriteValue(object value)
        {
            _cache.Add(new ValueWriteCommand(value));
        }


        public void WriteNumber(int number)
        {
            _cache.Add(new NumberWriteCommand(number));
        }

        public void WriteNumbers(int[] numbers)
        {
            _cache.Add(new NumbersWriteCommand(numbers));
        }

        public void Open(Stream stream)
        {
            _stream = stream;
            _cache = new List<WriteCommand>();
            _types = new IndexGenerator<Type>();
            _names = new IndexGenerator<string>();
        }

        public void Close()
        {
            var writer = new BinaryWriter(_stream, _encoding);

            // Write Names
            WriteNamesHeader(writer);

            // Write Types
            WriteTypesHeader(writer);

            // Write Data
            WriteCache(_cache, writer);

            writer.Flush();
        }

        private static void WriteCache(List<WriteCommand> cache, BinaryWriter writer)
        {
            foreach (WriteCommand command in cache)
            {
                command.Write(writer);
            }
        }

        private void WriteNamesHeader(BinaryWriter writer)
        {
            // count
            BinaryWriterHelpers.WriteNumber(_names.Items.Count, writer);

            // Items
            foreach (string name in _names.Items)
            {
                BinaryWriterHelpers.WriteString(name, writer);
            }
        }

        private void WriteTypesHeader(BinaryWriter writer)
        {
            // count
            BinaryWriterHelpers.WriteNumber(_types.Items.Count, writer);

            // Items
            foreach (Type type in _types.Items)
            {
                string typeName = _typeNameConverter.ConvertToTypeName(type);
                BinaryWriterHelpers.WriteString(typeName, writer);
            }
        }

        private sealed class ByteWriteCommand : WriteCommand
        {
            public ByteWriteCommand(byte data)
            {
                Data = data;
            }

            public byte Data { get; set; }

            public override void Write(BinaryWriter writer)
            {
                writer.Write(Data);
            }
        }

        private sealed class NumberWriteCommand : WriteCommand
        {
            public NumberWriteCommand(int data)
            {
                Data = data;
            }

            public int Data { get; set; }

            public override void Write(BinaryWriter writer)
            {
                BinaryWriterHelpers.WriteNumber(Data, writer);
            }
        }
        private sealed class NumbersWriteCommand : WriteCommand
        {
            public NumbersWriteCommand(int[] data)
            {
                Data = data;
            }

            public int[] Data { get; set; }

            public override void Write(BinaryWriter writer)
            {
                BinaryWriterHelpers.WriteNumbers(Data, writer);
            }
        }

        private sealed class ValueWriteCommand : WriteCommand
        {
            public ValueWriteCommand(object data)
            {
                Data = data;
            }

            public object Data { get; set; }

            public override void Write(BinaryWriter writer)
            {
                BinaryWriterHelpers.WriteValue(Data, writer);
            }
        }

        private abstract class WriteCommand
        {
            public abstract void Write(BinaryWriter writer);
        }
    }
}
